/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.androsz.electricsleepbeta.alarmclock;

import java.text.DateFormatSymbols;
import java.util.Calendar;

import android.content.Context;
import android.database.Cursor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.BaseColumns;

import com.androsz.electricsleepbeta.R;

public final class Alarm implements Parcelable {

	// ////////////////////////////
	// Column definitions
	// ////////////////////////////
	public static class Columns implements BaseColumns {
		/**
		 * The content:// style URL for this table
		 */
		public static final Uri CONTENT_URI = Uri
				.parse("content://com.androsz.electricsleepbeta.alarmclock/alarm");

		/**
		 * Hour in 24-hour localtime 0 - 23.
		 * <P>
		 * Type: INTEGER
		 * </P>
		 */
		public static final String HOUR = "hour";

		/**
		 * Minutes in localtime 0 - 59
		 * <P>
		 * Type: INTEGER
		 * </P>
		 */
		public static final String MINUTES = "minutes";

		/**
		 * Days of week coded as integer
		 * <P>
		 * Type: INTEGER
		 * </P>
		 */
		public static final String DAYS_OF_WEEK = "daysofweek";

		/**
		 * Alarm time in UTC milliseconds from the epoch.
		 * <P>
		 * Type: INTEGER
		 * </P>
		 */
		public static final String ALARM_TIME = "alarmtime";

		/**
		 * True if alarm is active
		 * <P>
		 * Type: BOOLEAN
		 * </P>
		 */
		public static final String ENABLED = "enabled";

		/**
		 * True if alarm should vibrate
		 * <P>
		 * Type: BOOLEAN
		 * </P>
		 */
		public static final String VIBRATE = "vibrate";

		/**
		 * Message to show when alarm triggers Note: not currently used
		 * <P>
		 * Type: STRING
		 * </P>
		 */
		public static final String MESSAGE = "message";

		/**
		 * Audio alert to play when alarm triggers
		 * <P>
		 * Type: STRING
		 * </P>
		 */
		public static final String ALERT = "alert";

		/**
		 * Time to ignore - used for ignoring alarms that would normally happen
		 * after smart alarm
		 * <P>
		 * Type: STRING
		 * </P>
		 */
		public static final String TIME_TO_IGNORE = "timeToIgnore";

		/**
		 * The default sort order for this table
		 */
		public static final String DEFAULT_SORT_ORDER = HOUR + ", " + MINUTES
				+ " ASC";

		// Used when filtering enabled alarms.
		public static final String WHERE_ENABLED = ENABLED + "=1";

		static final String[] ALARM_QUERY_COLUMNS = { _ID, HOUR, MINUTES,
				DAYS_OF_WEEK, ALARM_TIME, ENABLED, VIBRATE, MESSAGE, ALERT,
				TIME_TO_IGNORE };

		/**
		 * These save calls to cursor.getColumnIndexOrThrow() THEY MUST BE KEPT
		 * IN SYNC WITH ABOVE QUERY COLUMNS
		 */
		public static final int ALARM_ID_INDEX = 0;
		public static final int ALARM_HOUR_INDEX = 1;
		public static final int ALARM_MINUTES_INDEX = 2;
		public static final int ALARM_DAYS_OF_WEEK_INDEX = 3;
		public static final int ALARM_TIME_INDEX = 4;
		public static final int ALARM_ENABLED_INDEX = 5;
		public static final int ALARM_VIBRATE_INDEX = 6;
		public static final int ALARM_MESSAGE_INDEX = 7;
		public static final int ALARM_ALERT_INDEX = 8;
		public static final int ALARM_TIME_TO_IGNORE_INDEX = 9;

	}

	/*
	 * Days of week code as a single int. 0x00: no day 0x01: Monday 0x02:
	 * Tuesday 0x04: Wednesday 0x08: Thursday 0x10: Friday 0x20: Saturday 0x40:
	 * Sunday
	 */
	static final class DaysOfWeek {

		private static int[] DAY_MAP = new int[] { Calendar.MONDAY,
				Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY,
				Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY, };

		// Bitmask of all repeating days
		private int mDays;

		DaysOfWeek(final int days) {
			mDays = days;
		}

		// Returns days of week encoded in an array of booleans.
		public boolean[] getBooleanArray() {
			final boolean[] ret = new boolean[7];
			for (int i = 0; i < 7; i++) {
				ret[i] = isSet(i);
			}
			return ret;
		}

		public int getCoded() {
			return mDays;
		}

		/**
		 * returns number of days from today until next alarm
		 * 
		 * @param c
		 *            must be set to today
		 */
		public int getNextAlarm(final Calendar c) {
			if (mDays == 0)
				return -1;

			final int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;

			int day = 0;
			int dayCount = 0;
			for (; dayCount < 7; dayCount++) {
				day = (today + dayCount) % 7;
				if (isSet(day)) {
					break;
				}
			}
			return dayCount;
		}

		public boolean isRepeatSet() {
			return mDays != 0;
		}

		private boolean isSet(final int day) {
			return (mDays & 1 << day) > 0;
		}

		public void set(final DaysOfWeek dow) {
			mDays = dow.mDays;
		}

		public void set(final int day, final boolean set) {
			if (set) {
				mDays |= 1 << day;
			} else {
				mDays &= ~(1 << day);
			}
		}

		public String toString(final Context context, final boolean showNever) {
			final StringBuilder ret = new StringBuilder();

			// no days
			if (mDays == 0)
				return showNever ? context.getText(R.string.never).toString()
						: "";

			// every day
			if (mDays == 0x7f)
				return context.getText(R.string.every_day).toString();

			// count selected days
			int dayCount = 0, days = mDays;
			while (days > 0) {
				if ((days & 1) == 1) {
					dayCount++;
				}
				days >>= 1;
			}

			// short or long form?
			final DateFormatSymbols dfs = new DateFormatSymbols();
			final String[] dayList = dayCount > 1 ? dfs.getShortWeekdays()
					: dfs.getWeekdays();

			// selected days
			for (int i = 0; i < 7; i++) {
				if ((mDays & 1 << i) != 0) {
					ret.append(dayList[DAY_MAP[i]]);
					dayCount -= 1;
					if (dayCount > 0) {
						ret.append(context.getText(R.string.day_concat));
					}
				}
			}
			return ret.toString();
		}
	}

	// ////////////////////////////
	// Parcelable apis
	// ////////////////////////////
	public static final Parcelable.Creator<Alarm> CREATOR = new Parcelable.Creator<Alarm>() {
		@Override
		public Alarm createFromParcel(final Parcel p) {
			return new Alarm(p);
		}

		@Override
		public Alarm[] newArray(final int size) {
			return new Alarm[size];
		}
	};

	// ////////////////////////////
	// end Parcelable apis
	// ////////////////////////////

	// Public fields
	public int id;

	// ////////////////////////////
	// End column definitions
	// ////////////////////////////

	public boolean enabled;
	public int hour;
	public int minutes;
	public DaysOfWeek daysOfWeek;
	public long time;
	public boolean vibrate;
	public String label;
	public Uri alert;
	public boolean silent;
	public long timeToIgnore;

	// Creates a default alarm at the current time.
	public Alarm() {
		id = -1;
		final Calendar c = Calendar.getInstance();
		c.setTimeInMillis(System.currentTimeMillis());
		hour = c.get(Calendar.HOUR_OF_DAY);
		minutes = c.get(Calendar.MINUTE);
		vibrate = true;
		daysOfWeek = new DaysOfWeek(0);
		alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
		timeToIgnore = 0;

	}

	public Alarm(final Cursor c) {
		id = c.getInt(Columns.ALARM_ID_INDEX);
		enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1;
		hour = c.getInt(Columns.ALARM_HOUR_INDEX);
		minutes = c.getInt(Columns.ALARM_MINUTES_INDEX);
		daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX));
		time = c.getLong(Columns.ALARM_TIME_INDEX);
		vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1;
		label = c.getString(Columns.ALARM_MESSAGE_INDEX);
		timeToIgnore = c.getLong(Columns.ALARM_TIME_TO_IGNORE_INDEX);
		final String alertString = c.getString(Columns.ALARM_ALERT_INDEX);
		if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) {
			if (Log.LOGV) {
				Log.v("Alarm is marked as silent");
			}
			silent = true;
		} else {
			if (alertString != null && alertString.length() != 0) {
				alert = Uri.parse(alertString);
			}

			// If the database alert is null or it failed to parse, use the
			// default alert.
			if (alert == null) {
				alert = RingtoneManager
						.getDefaultUri(RingtoneManager.TYPE_ALARM);
			}
		}
	}

	public Alarm(final Parcel p) {
		id = p.readInt();
		enabled = p.readInt() == 1;
		hour = p.readInt();
		minutes = p.readInt();
		daysOfWeek = new DaysOfWeek(p.readInt());
		time = p.readLong();
		vibrate = p.readInt() == 1;
		label = p.readString();
		alert = (Uri) p.readParcelable(null);
		silent = p.readInt() == 1;
		timeToIgnore = p.readLong();
	}

	@Override
	public int describeContents() {
		return 0;
	}

	public String getLabelOrDefault(final Context context) {
		if (label == null || label.length() == 0)
			return context.getString(R.string.default_label);
		return label;
	}

	@Override
	public void writeToParcel(final Parcel p, final int flags) {
		p.writeInt(id);
		p.writeInt(enabled ? 1 : 0);
		p.writeInt(hour);
		p.writeInt(minutes);
		p.writeInt(daysOfWeek.getCoded());
		p.writeLong(time);
		p.writeInt(vibrate ? 1 : 0);
		p.writeString(label);
		p.writeParcelable(alert, flags);
		p.writeInt(silent ? 1 : 0);
		p.writeLong(timeToIgnore);
	}
}
